﻿using System;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NAnt.Core;
using NAnt.Core.Types;
using NAnt.Core.Attributes;

namespace NAntExt.Functions
{
    /// <summary>
    /// NAnt Functions for path.
    /// </summary>
    [FunctionSet("path", "Path")]
    public class PathFunctions : FunctionSetBase
    {
        #region Public Instance Constructors

        public PathFunctions(Project project, PropertyDictionary properties)
            : base(project, properties)
        {
        }

        #endregion Public Instance Constructors

        /// <summary>
        /// Converts a network path or local path to a local path. Converts:
        ///     From \\foomachine\c$\temp\something TO c:\temp\something 
        ///     OR 
        ///     From \\foomachine\sharename\temp\something TO \temp\something
        ///     OR 
        ///     From c:\temp\something TO c:\temp\something
        /// </summary>
        /// <param name="path">The network path to convert.</param>
        /// <returns>Returns the local path.</returns>
        [Function("to-local-path")]
        public static string ToLocalPath(string path)
        {
            Match pathMatch = Regex.Match(path, @"(\\\\[^\\]+\\)?(?<drive>(?<driveLetter>[A-Za-z])[:$])?(?<path>.*)");
            if (pathMatch.Success)
            {
                var resultPath = new StringBuilder();
                
                Group driveLetterGroup = pathMatch.Groups["driveLetter"];
                if (driveLetterGroup.Success)
                {
                    resultPath.Append(driveLetterGroup.Value)
                        .Append(":\\");
                }

                Group pathGroup = pathMatch.Groups["path"];
                var pathFrag = pathGroup.Value;
                if (driveLetterGroup.Success && !string.IsNullOrEmpty(pathFrag))
                {
                    pathFrag = pathFrag.TrimStart('\\');
                }
                resultPath.Append(pathFrag);

                return resultPath.ToString();
            }
            else
            {
                throw new System.ArgumentOutOfRangeException("path", path, "Path does not conform to a known syntax. Must be like \\\\host\\c$\\foo\\bar OR c:\\foo\\bar.");
            }
        }

        /// <summary>
        /// Gets the target of the symbolic link.
        /// </summary>
        /// <param name="path">The full path of the directory or file.</param>
        /// <returns>Returns the target else empty string if it cannot be determined.</returns>
        [Function("get-symbolic-link-target")]
        public static string GetSymbolicLinkTarget(string path)
        {
            // do a dir of the parent directory and parse the output
            var linkMatch = GetSymbolicLinkMatch(path);
            if (linkMatch != null)
            {
                return linkMatch.Groups["Target"].Value.Replace("\n", "");
            }
            else
            {
                return string.Empty;
            }
        }

        /// <summary>
        /// Gets the type of the symbolic link.
        /// </summary>
        /// <param name="path">The full path of the directory or file.</param>
        /// <returns>Returns the type else empty string if it cannot be determined.</returns>
        [Function("get-symbolic-link-type")]
        public static string GetSymbolicLinkType(string path)
        {
            // do a dir of the parent directory and parse the output
            var linkMatch = GetSymbolicLinkMatch(path);
            if (linkMatch != null)
            {
                return linkMatch.Groups["Type"].Value.Replace("\n", "");
            }
            else
            {
                return string.Empty;
            }
        }

        /// <summary>
        /// Get a regular expression match from parsing a dir on the parent folder
        /// which conatins the symbolic link.
        /// </summary>
        /// <param name="path">The full path to the directory or file.</param>
        /// <returns>Returns the match if found else null.</returns>
        private static Match GetSymbolicLinkMatch(string path)
        {
            string parentDirectory;
            string pathName = Path.GetFileName(path);
            if (Directory.Exists(path))
            {
                parentDirectory = (new DirectoryInfo(path)).Parent.FullName;
            }
            else if (File.Exists(path))
            {
                parentDirectory = Path.GetDirectoryName(path);
            }
            else
            {
                return null;
            }
            var dirStartInfo = new ProcessStartInfo()
            {
                FileName = "cmd.exe",
                WorkingDirectory = parentDirectory,
                Arguments = "/C dir *",
                CreateNoWindow = true,
                WindowStyle = ProcessWindowStyle.Hidden,
                UseShellExecute = false,
            };
            dirStartInfo.RedirectStandardOutput = true;
            var dirProcess = new Process();
            dirProcess.StartInfo = dirStartInfo;
            dirProcess.Start();
            dirProcess.WaitForExit(30000);
            string dirOutput = dirProcess.StandardOutput.ReadToEnd();
            var linkMatches = SymbolicLinkRegex.Matches(dirOutput);
            foreach (Match linkMatch in linkMatches)
            {
                var typeGroup = linkMatch.Groups["Type"];
                var nameGroup = linkMatch.Groups["Name"];
                var targetGroup = linkMatch.Groups["Target"];
                var name = nameGroup.Value.Replace("\n", "");
                if (string.Equals(name, pathName, StringComparison.InvariantCultureIgnoreCase))
                {
                    return linkMatch;
                }
            }
            return null;
        }

        /// <summary>
        /// Regular expression to parse the output of the dir command for symbolic link pattern.
        /// </summary>
        private static readonly Regex SymbolicLinkRegex = new Regex(@"^\d+/\d+/\d+[ ]+\d+:\d+\s[AP]M[ ]+\<(?'Type'[^>]+)\>[ ]+(?'Name'.+?)(?=\s\[)\s\[(?'Target'[^]]+)",
                RegexOptions.Multiline |
                RegexOptions.Compiled);
    }
}
